from typing import Union, List, Optional, TypedDict
import time
import random
import os

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, ElementNotInteractableException
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
import pymorphy2

from . import browser as browser_module
from .utils import message_to_list_of_parsing_actions
from .utils import add_message_to_list_of_parser_action


class Application(TypedDict):
    id: str
    description: str


class StopWordPymorphy:
    """ Работа со стоп словами с помощью библиотеки pymorphy """

    def get_word_in_all_cases(
        self,
        morph: pymorphy2.MorphAnalyzer,
        word: str
    ) -> List[str]:
        """ Получаем слово во всех падежах """

        word = morph.parse(word)[0]
        word_in_all_cases = [word.word for word in word.lexeme]
        return word_in_all_cases


class ScraperSelenium:
    """ Инструменты для парсинга относящиеся к selenium """

    def wait(self, f=2, b=3.5):
        """ Чуть-чуть подождать """

        number = random.uniform(f, b)
        print(f'Ждем {number}\n')
        time.sleep(number)


    def scroll_the_page(self, browser, a=.2, b=.5, w=0, h=50):
        """
        Прокрутить страницу js
        w - прокрутить страницу по ширине
        h - прокрутить страницу по высоте
        """

        browser.execute_script(f"window.scrollBy({w}, {h})")
        self.wait(a, b)


    def scroll_full_page(self, browser: webdriver.Chrome) -> None:
        """ Пролистываем всю страницу """

        browser.execute_script(
            "window.scrollTo(0, document.body.scrollHeight);"
        )

    def get_page_element_wait(
        self,
        browser: webdriver.Chrome,
        xpath: str
    ) -> WebElement:
        """ Получаем элемент старницы ожидая его появления """

        while True:
            try:
                element = browser.find_element(By.XPATH, xpath)
                return element
            except NoSuchElementException as error:
                message = f'Элемент по {xpath} не был найден, ждем...'
                print(message)
                time.sleep(1)


    def get_page_element_or_elements(
        self,
        browser: webdriver.Chrome,
        xpath: str,
        list_of_elements: bool = False
    ) -> Union[WebElement, List[WebElement]]:
        """ Получаем элемент/элементы страницы """

        try:
            if list_of_elements:
                result = browser.find_elements(By.XPATH, xpath)
            else:
                result = browser.find_element(By.XPATH, xpath)
        except NoSuchElementException as error:
            print(error)
            message = f'Элемент по {xpath} не был найден'
            raise ValueError(message)

        return result


    def enter_text_in_field(
        self,
        browser: webdriver.Chrome,
        xpath: str,
        text: str,
        is_press_enter: bool = False,
        a: Union[int, float] = .5,
        b: Union[int, float] = .8
    ) -> None:
        """ Вводим текст в указанное поле """

        element = self.get_page_element_wait(browser, xpath)
        element.click()
        self.wait(a, b)
        element.clear()

        for symbol in text:
            element.send_keys(symbol)
            self.wait(a, b)

        if is_press_enter:
            element.send_keys(Keys.ENTER)


    def click_on_element(
        self,
        browser: webdriver.Chrome,
        xpath: Optional[str] = None,
        element: Optional[WebElement] = None
    ) -> None:
        """ Кликаем по элементу """

        while True:
            try:
                if xpath:
                    element = self.get_page_element_wait(browser, xpath)

                actions = ActionChains(browser)
                actions.move_to_element(element)
                actions.perform()
                element.click()
                self.wait(1, 2)
                break
            except (ElementNotInteractableException):
                print(f'Ждем появление элемента {xpath}')
                time.sleep(15)


    def switch_frame(self, browser: webdriver.Chrome, xpath: str) -> None:
        """ Переключаем на другой фрейм """

        frame_element = self.get_page_element_wait(
            browser,
            xpath
        )
        browser.switch_to.frame(frame_element)


    def go_back_to_main_window(self, browser: webdriver.Chrome) -> None:
        """ Возвращаемся к главному окну после переключения frame """
        browser.switch_to.default_content()


class Scraper(ScraperSelenium):

    def __init__(self):
        self.PAGE_WITH_APPLICATIONS = 'https://mp24.bitrix24.ru/marketplace/app/10/'


    @message_to_list_of_parsing_actions(
        message='Выполняем авторизацию на сайте'
    )
    def login_to_site(self, browser: webdriver.Chrome) -> webdriver.Chrome:
        """ Авторизируемся на сайте """

        def enter_login(browser: webdriver.Chrome) -> None:
            """ Вводим логин пользователя """

            user_login = os.getenv('BITRIX_LOGIN')
            self.enter_text_in_field(
                browser,
                '//input[@id="login"]',
                user_login,
                is_press_enter=True
            )


        def enter_password(browser: webdriver.Chrome) -> None:
            """ Вводим пароль пользователя """

            user_password = os.getenv('BITRIX_PASSWORD')
            self.enter_text_in_field(
                browser,
                '//input[@id="password"]',
                user_password,
                is_press_enter=True
            )


        # ВЫШЕ ОПРЕДЕЛЕНИЕ ФУНКЦИЙ

        browser.get(self.PAGE_WITH_APPLICATIONS)
        self.wait(1, 2)
        enter_login(browser)
        self.wait(1, 2)
        enter_password(browser)


    def switch_to_list_of_applications_by_frame(
        self,
        browser: webdriver.Chrome
    ) -> None:
        """ Переключаемся к списку заявок по фреймам """

        self.switch_frame(
            browser,
            '//div[@id="workarea-content"]//iframe'
        )
        self.switch_frame(
            browser,
            '//iframe[@name="partner_application"]'
        )


    @message_to_list_of_parsing_actions(
        message='Открываем страницу со списком заявок'
    )
    def open_list_with_applications(self, browser: webdriver.Chrome) -> None:
        """ Открываем список с заявками """

        def select_filters(browser: webdriver.Chrome) -> None:
            """ Выбираем фильтры """

            self.click_on_element(
                browser,
                xpath='//input[@id="b24_partner_application_filter_search"]'
            )


        def choose_direction(browser: webdriver.Chrome) -> None:
            """ Выбираем направление """

            self.get_page_element_wait(
                browser,
                xpath='//span[contains(text(), "Не указан")]'
            ).click()


        def choose_bitrix24(browser: webdriver.Chrome) -> None:
            """ Выбираем Битрикс24 """

            self.get_page_element_wait(
                browser,
                '//div[@data-indicator="B24"]'
            ).click()


        # ВЫШЕ ОПРЕДЕЛЕНИЕ ФУНКЦИЙ

        self.scroll_full_page(browser)
        self.switch_to_list_of_applications_by_frame(browser)

        select_filters(browser)
        choose_direction(browser)
        choose_bitrix24(browser)

        self.go_back_to_main_window(browser)


    def open_application_page(self) -> webdriver.Chrome:
        """ Открываем страницу с заявками """

        browser = browser_module.Browser().get_browser(display_browser=True)
        self.login_to_site(browser)
        self.wait(9, 10)
        self.open_list_with_applications(browser)
        return browser


    def get_list_of_applications(
        self,
        browser: webdriver.Chrome
    ) -> List[Application]:
        """ Получаем список описаний заявок """

        list_of_application = []
        list_of_application_elements = self.get_page_element_or_elements(
            browser,
            '//table[@id="b24_partner_application_table"]//tr[@data-id]',
            list_of_elements=True
        )

        for application in list_of_application_elements:

            id = application.get_attribute('data-id')
            description = application.text

            application = Application(
                id=id,
                description=description
            )
            list_of_application.append(application)

        return list_of_application


    def filter_list_of_applications(
        self,
        morph: pymorphy2.MorphAnalyzer,
        list_of_applications: List[Application],
        stop_words: str
    ) -> List[Application]:
        """ Фильтруем список заявок """

        def check_stop_word(description: str) -> bool:
            """ Проверяем если ли стоп слово в описание заявки """

            description = description.lower()

            for stop_word in list_of_stop_words:

                stop_word = stop_word.lower()
                word_in_all_cases = StopWordPymorphy().get_word_in_all_cases(
                    morph,
                    stop_word
                )

                for word in word_in_all_cases:
                    if word in description: return True


        # ВЫШЕ ОПРЕДЕЛЕНИЕ ФУНКЦИЙ

        filtered_applications = []
        list_of_stop_words = [word.strip() for word in stop_words.split(',')]

        for application in list_of_applications:
            if check_stop_word(application['description']): continue
            filtered_applications.append(application)

        return filtered_applications


    def take_filetered_applications(
        self,
        browser: webdriver.Chrome,
        list_of_applications: List[Application]
    ) -> None:
        """
        Берем все отфильтрованные заявки, нажимая на кнопку 'Взять заявку'
        """

        def get_button_to_select_application(
            browser: webdriver.Chrome,
            application: Application
        ) -> Optional[WebElement]:
            """ Получаем кнопку для выбора заявки """

            id = application['id']
            xpath = f'//table[@id="b24_partner_application_table"]//tr[@data-id="{id}"]'

            try:
                button = browser.find_element(By.XPATH, xpath)
                return button
            except NoSuchElementException:
                return None


        # ВЫШЕ ОПРЕДЕЛЕНИЕ ФУНКЦИЙ

        for application in list_of_applications:

            button_to_select_application = get_button_to_select_application(
                browser,
                application
            )
            if button_to_select_application:
                part_of_application = application['description'][:60] + '...'
                message = f'Берем заявку: {part_of_application}'
                add_message_to_list_of_parser_action(message)
                # self.click_on_element(
                #    browser,
                #    element=button_to_select_application
                # )


    @message_to_list_of_parsing_actions(
        message='Начинаем анализировать имеющиеся заявки'
    )
    def start_collection_applications(
        self,
        browser: webdriver.Chrome,
        morph: pymorphy2.MorphAnalyzer,
        stop_words: str
    ) -> None:
        """ Начинаем сбор заявок """

        browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        self.wait(2, 3)
        self.switch_to_list_of_applications_by_frame(browser)

        list_of_applications = self.get_list_of_applications(browser)
        list_of_applications = self.filter_list_of_applications(
            morph,
            list_of_applications,
            stop_words
        )
        self.take_filetered_applications(browser, list_of_applications)

        self.go_back_to_main_window(browser)


    @message_to_list_of_parsing_actions(
        message='Ожидаем следующего обновления списка заявок'
    )
    def update_list_of_applications(self, browser: webdriver.Chrome) -> None:
        """ Обновляем список заявок """

        self.switch_to_list_of_applications_by_frame(browser)

        self.click_on_element(
           browser,
           xpath='//span[@class="main-ui-item-icon main-ui-search"]'
        )
        self.wait(5, 7)

        self.go_back_to_main_window(browser)
